﻿using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using Avalonia.Controls.Primitives;
using Avalonia.Input;

namespace KinonekoSoftware.Studio.Resources.Converters
{
    public class DragBehavior : AvaloniaObject
    {
        static DragBehavior()
        {
            IsDragEnabledProperty = AvaloniaProperty.RegisterAttached<DragBehavior, TemplatedControl, bool>("IsDragEnabled");
            IsDraggingProperty    = AvaloniaProperty.RegisterAttached<DragBehavior, TemplatedControl, bool>("IsDragging");
            XProperty             = AvaloniaProperty.RegisterAttached<DragBehavior, TemplatedControl, double>("X");
            YProperty             = AvaloniaProperty.RegisterAttached<DragBehavior, TemplatedControl, double>("Y");
            OriginalXProperty     = AvaloniaProperty.RegisterAttached<DragBehavior, TemplatedControl, double>("OriginalX");
            OriginalYProperty     = AvaloniaProperty.RegisterAttached<DragBehavior, TemplatedControl, double>("OriginalY");

            IsDragEnabledProperty.Changed.Subscribe(OnIsDragEnabledPropertyChanged);
        }

        #region IsDragEnabled

        /// <summary>
        /// Dragging enable state attached dependency property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.NotNull]
        public static readonly StyledProperty<bool> IsDragEnabledProperty;

        /// <summary>
        /// Gets the dragging enabled attached property value.
        /// </summary>
        public static bool GetIsDragEnabled(AvaloniaObject obj)
        {
            return obj.GetValue(IsDragEnabledProperty);
        }

        /// <summary>
        /// Sets the dragging enabled attached property value.
        /// </summary>
        public static void SetIsDragEnabled(AvaloniaObject obj, bool value)
        {
            obj.SetValue(IsDragEnabledProperty, value);
        }

        private static void OnIsDragEnabledPropertyChanged(AvaloniaPropertyChangedEventArgs<bool> args)
        {
            var element = args.Sender as TemplatedControl;
            if (element is null)
            {
                return;
            }

            if (args.GetNewValue<bool>())
            {
                // Register the event handlers
                element.PointerPressed  += OnDragStarted;
                element.PointerReleased += OnDragFinished;
            }
            else
            {
                // Un-register the event handlers
                element.PointerPressed  -= OnDragStarted;
                element.PointerReleased -= OnDragFinished;
            }
        }

        #endregion

        #region IsDragging

        /// <summary>
        /// Dragging state attached dependency property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.NotNull]
        public static readonly StyledProperty<bool> IsDraggingProperty;

        /// <summary>
        /// Gets the dragging attached property value.
        /// </summary>
        public static bool GetIsDragging(AvaloniaObject obj)
        {
            return obj.GetValue(IsDraggingProperty);
        }

        /// <summary>
        /// Sets the dragging attached property value.
        /// </summary>
        public static void SetIsDragging(AvaloniaObject obj, bool value)
        {
            obj.SetValue(IsDraggingProperty, value);
        }

        #endregion

        #region X

        /// <summary>
        /// X position attached dependency property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.NotNull]
        public static readonly StyledProperty<double> XProperty;

        /// <summary>
        /// Gets the X attached property value.
        /// </summary>
        public static double GetX(AvaloniaObject obj)
        {
            return obj.GetValue(XProperty);
        }

        /// <summary>
        /// Sets the X attached property value.
        /// </summary>
        public static void SetX(AvaloniaObject obj, double value)
        {
            obj.SetValue(XProperty, value);
        }

        #endregion

        #region Y

        /// <summary>
        /// Y position attached dependency property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.NotNull]
        public static readonly StyledProperty<double> YProperty;

        /// <summary>
        /// Gets the Y attached property value.
        /// </summary>
        public static double GetY(AvaloniaObject obj)
        {
            return obj.GetValue(YProperty);
        }

        /// <summary>
        /// Sets the Y attached property value.
        /// </summary>
        public static void SetY(AvaloniaObject obj, double value)
        {
            obj.SetValue(YProperty, value);
        }

        #endregion

        #region OriginalX

        [System.Diagnostics.CodeAnalysis.NotNull]
        private static readonly StyledProperty<double> OriginalXProperty;

        private static double GetOriginalX(AvaloniaObject obj)
        {
            return obj.GetValue(OriginalXProperty);
        }

        private static void SetOriginalX(AvaloniaObject obj, double value)
        {
            obj.SetValue(OriginalXProperty, value);
        }

        #endregion

        #region OriginalY

        [System.Diagnostics.CodeAnalysis.NotNull]
        private static readonly StyledProperty<double> OriginalYProperty;

        private static double GetOriginalY(AvaloniaObject obj)
        {
            return obj.GetValue(OriginalYProperty);
        }

        private static void SetOriginalY(AvaloniaObject obj, double value)
        {
            obj.SetValue(OriginalYProperty, value);
        }

        #endregion

        #region Handlers

        private static void OnDragStarted([System.Diagnostics.CodeAnalysis.NotNull] object sender, [System.Diagnostics.CodeAnalysis.NotNull] PointerPressedEventArgs args)
        {
            var obj = (TemplatedControl)sender;
            // We are starting the drag
            SetIsDragging(obj, true);

            var position = args.GetPosition(obj);

            // Save the position of the mouse to the start position
            SetOriginalX(obj, position.X);
            SetOriginalY(obj, position.Y);

            // Capture the mouse
            obj.PointerMoved += OnDragging;
            args.Handled     =  true;
        }

        private static void OnDragging(object sender, PointerEventArgs args)
        {
            var obj = (TemplatedControl)sender;
            if (!GetIsDragging(obj))
                return;

            var position         = args.GetPosition(obj);
            var horizontalChange = position.X - GetOriginalX(obj);
            var verticalChange   = position.Y - GetOriginalY(obj);

            if (double.IsNaN(GetX(obj)))
            {
                SetX(obj, 0);
            }

            if (double.IsNaN(GetY(obj)))
            {
                SetY(obj, 0);
            }

            // Move the object
            SetX(obj, GetX(obj) + horizontalChange);
            SetY(obj, GetY(obj) + verticalChange);

            args.Handled = true;
        }

        private static void OnDragFinished(object sender, PointerReleasedEventArgs args)
        {
            var obj = (TemplatedControl)sender;
            SetIsDragging(obj, false);
            obj.ClearValue(OriginalXProperty);
            obj.ClearValue(OriginalYProperty);

            obj.PointerMoved -= OnDragging;

            args.Handled = true;
        }

        #endregion
    }
}